# Copyright (c) 2009 by Alex Leone <acleone ~at~ gmail.com>
#
# This file is part of epp.
#
# epp is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# epp is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with epp.  If not, see <http://www.gnu.org/licenses/>.

r"""
Contains the [for] tag.

Syntax:
    [for local_var in list:]
        statements (with [local_var])
    [/for sep="," outsep="\n"]

Attributes:
    -- local_var: the name to use for the local variable inside
                  the list.
    -- in: the second attribute is required to be 'in'
    -- list: a defined tag or attribute.
             if list has newlines:
                 if there are blank lines:
                     split by blank lines
                 else:
                     split by newlines
             else:
                 split by commas, or the value of any attribute
                 that starts with 's' after list.
    -- zip: (optional) iterate over two lists using python's zip():
                       [for x y in l1 l2:]
                       [for x y in zip l1 l2:]
    -- sep/osep: attributes that follow list are parsed:
                 starts with s: change input seperator (default ',')
                 starts with o: change output seperator (default '\n')
                                Note that the output is joined, so
                                if you would like the output seperator
                                at the end of the result, you have to
                                manually append it.

Outputs:
    statements executed for the iteration of list, joined by '\n',
    or the value of any attribute that starts with 'o' after list

Examples:
    Note: s[1:] in p.parsetags(s[1:]) is to strip the leading '\n'
          from s

    >>> p = epp.Epp()
    >>> s = '''
    ... [define list="1,2,3,4,5"]
    ... [for x in list:]
    ...  [x]'''
    >>> print(p.parsetags(s[1:]))
    1
    2
    3
    4
    5
    >>> s = '''
    ... [define list2="1,2,3,4,5"]
    ... [for x in list2:]
    ...  [x]
    ... [/for output_sep=',']'''
    >>> print(p.parsetags(s[1:]))
    1,2,3,4,5
    >>> s = '''
    ... [define list3:]
    ...  1
    ...  2
    ...  3
    ... [for x in list3:]
    ...  [x]
    ... [/for outsep="/"]'''
    >>> print(p.parsetags(s[1:]))
    1/2/3
    >>> s = '''
    ... [define list4:]
    ...  1
    ...  1
    ...
    ...  2
    ...  2
    ...
    ...  3
    ...  3
    ... [for x in list4:]
    ...  [x]
    ... [/for o="\n--@--\n"]'''
    >>> print(p.parsetags(s[1:]))
    1
    1
    --@--
    2
    2
    --@--
    3
    3
    >>> # iterating over two lists at once
    >>> s = '''
    ... [define l1="1,2,3,4" l2="5,6,7,8"]
    ... [for x y in l1 l2:]
    ...  [y] - [x] == 4'''
    >>> print(p.parsetags(s[1:]))
    5 - 1 == 4
    6 - 2 == 4
    7 - 3 == 4
    8 - 4 == 4

Exceptions:
    SyntaxError -- the tag must have at least 3 attributes:
                  local_var, in, and list.

        >>> p.parsetags("[for]")
        Traceback (most recent call last):
            ...
        SyntaxError: [for] syntax should be '[for x in list:]'

    UndefinedTagError, UndefinedAttrError -- the list attribute is
                                             not defined

        >>> p.parsetags("[for i in notDefTag='']")
        Traceback (most recent call last):
            ...
        UndefinedTagError: [for] 'notDefTag' is not a defined tag
        >>> p.parsetags("[for i in !notDefAttr='']")
        Traceback (most recent call last):
            ...
        UndefinedAttrError: [for] '!notDefAttr' is not a defined attribute
"""

import re
import epp
import tags
from tags.tag import Tag
from tags.errors import *

tagnames = ["for"]

FOR_LIST_SEPERATOR = re.compile(r",")
LOCAL_VAR = re.compile(r"\[([a-zA-Z_]\w*)\]")

def getlistitems(eppinst, tagName, listName, itemsep, outputsep):
    """
    Returns the items in the defined tag/attribute listName.
    """
    if listName.startswith('!'):
        # this is a list defined via attribute
        listName = listName[1:]
        if not eppinst.hasDefinedAttr(listName):
            raise UndefinedAttrError(
                    "[%s] '!%s' is not a defined attribute"
                    % (tagName, listName))
            return
        listValue = eppinst.get_defined_attr(listName)
        if listValue == None:
            listValue = ''
    else:
        if listName not in eppinst._tags:
            raise UndefinedTagError(
                    "[%s] %r is not a defined tag"
                    % (tagName, listName))
            return
        f = eppinst._tags[listName]
        listValue = f.replace(eppinst, listName, [])
    if len(listValue) == 0:
        return []
    values = []
    if '\n' in listValue:
        # split by '\n'
        multiLineValues = []
        blankLineSeen = False
        for s in listValue.splitlines():
            if len(s.strip()) == 0:
                # blank line means split by blank lines
                multiLineValues.append('\n'.join(values))
                values = []
            else:
                values.append(s)
        if len(multiLineValues) > 0:
            multiLineValues.append('\n'.join(values))
            values = multiLineValues
    else:
        # split by commas
        i = epp.lstrip(listValue, 0)
        maxI = len(listValue)
        while True:
            i, value = epp.get_attr_value(listValue, i, itemsep)
            values.append(value)
            i = epp.lstrip(listValue, i)
            if i >= maxI:
                break
            i = epp.lstrip(listValue, i + 1)
    return values

class For(Tag):

    def replace(self, eppinst, tagName, attrs):
        if len(attrs) < 3:
            raise SyntaxError(
                    "[%s] syntax should be '[for x in list:]'"
                    % (tagName))
            return
        localvars = []
        listNames = []
        forBody = None
        inSeen = False
        zipSeen = False
        itemsep = FOR_LIST_SEPERATOR
        outputsep = '\n'
        for varname, v in attrs:
            if not inSeen and varname == 'in' and v == None:
                inSeen = True
            elif inSeen:
                if varname == 'zip' and v == None and len(listNames) == 0:
                    zipSeen = True
                elif len(listNames) < len(localvars):
                    listNames.append(varname)
                    if len(listNames) == len(localvars):
                        forBody = v
                elif varname.startswith('s') and v != None:
                    itemsep = re.compile(v)
                elif varname.startswith('o') and v != None:
                    outputsep = v
            else:
                localvars.append(varname)
        if len(localvars) == 0 or len(listNames) == 0:
            raise SyntaxError(
                    "[%s] syntax should be '[for x in list:]'"
                    % (tagName))
            return
        if len(localvars) != len(listNames):
            raise SyntaxError(
                    "[%s] cannot unpack %d lists to %d local vars"
                    % (tagName, len(listNames), len(localvars)))
            return
        if forBody == None:
            eppinst._print_message(
                    "Warning: [%s] %r should probably have a `:' or value"
                    % (tagName, listName), 1)
            return ''
        listValues = []
        for listName in listNames:
            listValues.append(getlistitems(
                            eppinst, tagName, listName, itemsep, outputsep))
        forbodysplit = []
        localvarsi = [None]*len(localvars)
        for i, s in enumerate(LOCAL_VAR.split(forBody)):
            if i % 2 == 1:
                if s in localvars:
                    forbodysplit.append('')
                    lvili = localvars.index(s)
                    lvil = localvarsi[lvili]
                    if lvil == None:
                        lvil = [i]
                        localvarsi[lvili] = lvil
                    else:
                        lvil.append(i)
                else:
                    forbodysplit.append('[' + s + ']')
            else:
                forbodysplit.append(s)
        output = []
        for lvars in zip(*listValues):
            for i, lvar in enumerate(lvars):
                lvil = localvarsi[i]
                if lvil != None:
                    for j in lvil:
                        forbodysplit[j] = lvar
            output.append(eppinst.parsetags(''.join(forbodysplit)))
        return outputsep.join(output)